今天要介紹的是 Kubernetes 三兄弟的 Deployment,這個資源對象為 Pod 和 ReplicaSet 兩者提供了一個聲明式(declarative)定義的方法來達到使用者所期望的容器執行狀態,並且官方建議透過 Deployment 來佈署 Pod 和 ReplicaSet ,典型的應用場景包括:
Deployment
Pod 的介紹相信大家已經都不陌生了,但這邊怎麼又冒出一個 ReplicaSet 呢? ReplicaSet 是用來確保在資源允許的前提下,指定的 pod 的數量會跟使用者期望的一致,也就是所謂的 desired status ,而官方建議 ReplicaSet 要搭配 Deployment 一起來使用是因為 Deployment 是個更上層的抽象概念,也支援了更多好用的功能,因此官方才會建議不要單獨使用 ReplicaSet ,而是使用 Deployment 並且將其相關資訊設定在裡面。
從下圖可以看出三者在 Kubernetes 中的對應關係:

官方貼心的為我們提供了幾個經典的 Deployment 使用案例:
接下來我們將用以上情境來實戰演練一下~
// deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: foo-deployment
  labels:
    type: demo
spec:
  replicas: 1
  selector:
    matchLabels:
      type: demo
  template:
    metadata:
      labels:
        type: demo
    spec:
      containers:
        - name: foo
          image: mikehsu0618/foo
          ports:
            - containerPort: 8080
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: bar-deployment
  labels:
    type: demo
spec:
  replicas: 1
  selector:
    matchLabels:
      type: demo
  template:
    metadata:
      labels:
        type: demo
    spec:
      containers:
        - name: bar
          image: mikehsu0618/bar
          ports:
            - containerPort: 8080
kind : kind 選擇為 Deployment
spec.replicas : 被選擇套用的 Container 需要產生多少個 Pod,也是我們實現水平擴展的關鍵。spec.selector.matchLabels : 這裡就是寫入需要套用此 Deployment 的 Template Labels ,所以兩者必須相同。spec.template.metadata.labels : 設定 template.spec 的 Lables 。spec.template.spec.containers : 這裡就是我們熟悉的 Pod 相關設定。接著讓我們運行設定(設定檔沒有錯誤則可以如預期中的建立):
kubectl apply -f ./deployment.yaml
--------------------
deployment.apps/foo-deployment created
deployment.apps/bar-deployment created
使用指令確認一下:
kubectl get all
--------------------
NAME                                  READY   STATUS    RESTARTS   AGE
pod/bar-deployment-75bcfbd655-g5gwm   1/1     Running   0          5m59s
pod/foo-deployment-6bbf665b47-kfvxr   1/1     Running   0          5m59s
NAME                 TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
service/kubernetes   ClusterIP   10.96.0.1    <none>        443/TCP   23d
NAME                             READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/bar-deployment   1/1     1            1           5m59s
deployment.apps/foo-deployment   1/1     1            1           5m59s
NAME                                        DESIRED   CURRENT   READY   AGE
replicaset.apps/bar-deployment-75bcfbd655   1         1         1       5m59s
replicaset.apps/foo-deployment-6bbf665b47   1         1         1       5m59s
看到我們成功的運行起了 foo bar 兩個 Pod,並且建立了各自的 Deployment ReplicaSet 。
接下來我們使用來使用不同的方法更新已經運行起來的 Deployment 。
直接修改原有的設定檔:
apiVersion: apps/v1
kind: Deployment
metadata:
  name: foo-deployment
  labels:
    type: demo
spec:
	// 這裡我們將 Pod 擴展成兩個!
	===================
  replicas: 2
  ===================
  selector:
    matchLabels:
      type: demo
  template:
    metadata:
      labels:
        type: demo
    spec:
      containers:
        - name: foo
          image: mikehsu0618/foo
          ports:
            - containerPort: 8080
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: bar-deployment
  labels:
    type: demo
spec:
  replicas: 1
  selector:
    matchLabels:
      type: demo
  template:
    metadata:
      labels:
        type: demo
    spec:
      containers:
        - name: bar
          image: mikehsu0618/bar
          ports:
            - containerPort: 8080
修改完後再執行一次 apply 指令,kubectl 會檢查指定設定檔是否有更新:
kubectl apply -f ./deployment.yaml --record // --record 可以紀錄 rollout 歷史變更指令
-------------------------
deployment.apps/foo-deployment configured // 有更新
deployment.apps/bar-deployment unchanged  // 未檢查到更新
接著可以 kubectl rolloout status 查看我們對 foo-deployment 的資源管理狀態:
kubectl rollout status deployment foo-deployment 
-------------------------
deployment "foo-deployment" successfully rolled out
當指令顯示成功,即代表剛剛的更新已經正式生效~,但只要遇到設定錯誤或者是無法實現的請求時, rollout status 將會持續等待至 timeout。
我們也可以使用第二個方法「指令更新」來調整 Deployment :
kubectl scale deployment bar-deployment --replicas 3
而第三個方法為直接編輯在 Kubernetes 運行中的 Deployment 設定:
// 打開 commmand 編輯面板,直接修改設定
kubectl edit deploy bar-deployment
來使用 get all 確認看看吧。
kubectl get all
-------------------------
NAME                                  READY   STATUS    RESTARTS   AGE
pod/bar-deployment-75bcfbd655-75qcd   1/1     Running   0          31s
pod/bar-deployment-75bcfbd655-c5h9w   1/1     Running   0          31s
pod/bar-deployment-75bcfbd655-g5gwm   1/1     Running   0          5h52m
pod/foo-deployment-6bbf665b47-45c2k   1/1     Running   0          4h7m
pod/foo-deployment-6bbf665b47-kfvxr   1/1     Running   0          5h52m
NAME                 TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
service/kubernetes   ClusterIP   10.96.0.1    <none>        443/TCP   23d
NAME                             READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/bar-deployment   3/3     3            3           5h52m
deployment.apps/foo-deployment   2/2     2            2           5h52m
NAME                                        DESIRED   CURRENT   READY   AGE
replicaset.apps/bar-deployment-75bcfbd655   3         3         3       5h52m
replicaset.apps/foo-deployment-6bbf665b47   2         2         2       5h52m
我們在返回結果中可以看到 pod/bar-deployment  已經預期的啟動三個,並且 RepolicaSet 和 Deployment 也更新了對應狀態。
在我們更新 Deployment 時,Kubernetes 會產生一個 Deployment Revision ,可以很簡單的理解為是更新歷史版本,但要注意的是 不是每一次的更新都會產生 Revision ,只有在 Deployment created 以及 spec.template 範圍下的設定有更新才會產生,所以我們上面更新的 replicas=3 並不會出現在歷史中。
讓我們改動 spec.template 來實驗看看:
// deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: foo-deployment
  labels:
    type: demo
spec:
  replicas: 2
  selector:
    matchLabels:
      type: demo
  template:
    metadata:
      labels:
        type: demo
    spec:
      containers:
        - name: foo
          image: mikehsu0618/foo
          ports:
            - containerPort: 8080
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: bar-deployment
  labels:
    type: demo
spec:
  replicas: 3
  selector:
    matchLabels:
      type: demo
  // 只有在 spec.template 下的改定才會紀錄在 rollout history 中
  template:
    metadata:
      labels:
        type: demo
    spec:
      containers:
        - name: bar
			// 將我們的 image tag 版號改成不存在 `v1`
      ===================================
          image: mikehsu0618/bar:v1
      ===================================
          ports:
            - containerPort: 8080
更新 Deployment 設定檔並使用 --record 來紀錄指令:
kubectl apply -f deployment.yaml --record
-------------------------
Flag --record has been deprecated, --record will be removed in the future
deployment.apps/foo-deployment configured
deployment.apps/bar-deployment configured
原本的 spec.template 雖然會被紀錄在 rollout history 中,但不會有額外資訊,--record 可以讓 Kubernetes 幫我們記下我們當下改變設定的那個指令。
這時我們就能在 rollout history 查看產生出來的 revision :
kubectl rollout history deployment bar-deployment
--------------------------
REVISION  CHANGE-CAUSE
1         <none>
2         kubectl apply --filename=deployment.yaml --record=true
第一個版本為先前 Deployment 被建立時且沒有輸入 --record 的版本,第二個版本為我們調整 bar image=mikehsu0618/bar:v1 且有 --record 的版本。
指定 revision 並查看詳細資訊:
kubectl rollout history deployment bar-deployment --revision=2
--------------------------
deployment.apps/bar-deployment with revision #2ment --revision=2
Pod Template:
  Labels:       pod-template-hash=864b65d8b6
        type=demo
  Annotations:  kubernetes.io/change-cause: kubectl apply --filename=deployment.yaml --record=true
  Containers:
   bar:
    Image:      mikehsu0618/bar:v1
    Port:       8080/TCP
    Host Port:  0/TCP
    Environment:        <none>
    Mounts:     <none>
  Volumes:      <none>
接下來一樣是使用 get all 指令查看容器狀況:
kubectl get all
--------------------------
NAME                                  READY   STATUS             RESTARTS   AGE
pod/bar-deployment-75bcfbd655-5b9z5   1/1     Running            0          7m5s
pod/bar-deployment-75bcfbd655-6whzr   1/1     Running            0          7m5s
pod/bar-deployment-75bcfbd655-zk88d   1/1     Running            0          7m5s
pod/bar-deployment-864b65d8b6-wdhz9   0/1     ImagePullBackOff   0          6m34s
pod/foo-deployment-6bbf665b47-dhndq   1/1     Running            0          40m
pod/foo-deployment-6bbf665b47-pnjfs   1/1     Running            0          40m
NAME                 TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
service/kubernetes   ClusterIP   10.96.0.1    <none>        443/TCP   23d
NAME                             READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/bar-deployment   3/3     1            3           7m6s
deployment.apps/foo-deployment   2/2     2            2           40m
NAME                                        DESIRED   CURRENT   READY   AGE
replicaset.apps/bar-deployment-75bcfbd655   3         3         3       7m6s
replicaset.apps/bar-deployment-864b65d8b6   1         1         0       6m34s
replicaset.apps/foo-deployment-6bbf665b47   2         2         2       40m
這時我們會發現我們的 pod/bar-deployment 發生了 ImagePullBackOff ,原因是我們並沒有建立
mikehsu0618/bar:v1 的 image ,這種情況很好的提供我們一個因為 推進到一個不穩定的版本 而需要使用 版本回滾 先復原服務到上一個正常的版本。
使用 rollout 的回滾指令復原先前版本設定:
// 回滾至上個版本
kubectl rollout undo deployment bar-deployment --record
// 回滾至指定版本
kubectl rollout undo deployment bar-deployment --to-revision=1 --record
----------------------------
deployment.apps/bar-deployment rolled back
這時 Deployment 已經回到了,沒有出問題的 revision=1 版本了
kubectl get all
----------------------------
NAME                                  READY   STATUS    RESTARTS   AGE
pod/bar-deployment-75bcfbd655-5b9z5   1/1     Running   0          17m
pod/bar-deployment-75bcfbd655-6whzr   1/1     Running   0          17m
pod/bar-deployment-75bcfbd655-zk88d   1/1     Running   0          17m
pod/foo-deployment-6bbf665b47-dhndq   1/1     Running   0          50m
pod/foo-deployment-6bbf665b47-pnjfs   1/1     Running   0          50m
NAME                 TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
service/kubernetes   ClusterIP   10.96.0.1    <none>        443/TCP   23d
NAME                             READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/bar-deployment   3/3     3            3           17m
deployment.apps/foo-deployment   2/2     2            2           50m
NAME                                        DESIRED   CURRENT   READY   AGE
replicaset.apps/bar-deployment-75bcfbd655   3         3         3       17m
replicaset.apps/bar-deployment-864b65d8b6   0         0         0       16m
replicaset.apps/foo-deployment-6bbf665b47   2         2         2       50m
我們上面大致練習了幾個比較實用的方式,可以發現 Deployment 的設計非常的彈性以及簡潔,並且讓我們能將 Pod 設定在一起,大大的減少設定檔的數量。而 Deployment 因為可以簡單的設定 水平擴展 資源限制與請求 等操作,使得許多進階觀念 藍綠佈署 金絲雀佈署 得以更有可能的被一般的後端工程師實現(真是謝天謝地 wwww)。
千呼萬喚始出來!鐵人賽系列「從異世界歸來發現只剩自己不會 Kubernetes」同名改編作品出版了!
感謝所有交流指教的各路英雄,也感謝願意點閱文章的各位,如果能幫助到任何人都將會是我的榮幸。
本書內容改編自第 14 屆 iThome 鐵人賽 DevOps 組的優選系列文章《從異世界歸來發現只剩自己不會 Kubernetes》。此書是一本綜合性的指南,針對想要探索認識 Kubernetes 的技術人員而生。無論是初涉此領域的新手,還是已有深厚經驗的資深工程師,本書都能提供你所需的知識和技能。
「這本書不僅提供了豐富的範例程式碼和操作指南,讓身為工程師的我們能實際操作來加深認知;更重要的是,它教會我如何從後端工程師的角度去思考和應用 Kubernetes。從容器的生命週期、資源管理到部署管理,每一章都與我們的日常開發工作息息相關。」
──── 雷N │ 後端工程師 / iThome 鐵人賽戰友
天瓏連結: 從異世界歸來發現只剩自己不會 Kubernetes:初心者進入雲端世界的實戰攻略!
相關文章:
相關程式碼同時收錄在:
https://github.com/MikeHsu0618/2022-ithelp/tree/master/Day8
Reference
Kubernetes 教學系列 - 滾動更新就用 Deployment
Kubernetes Documentation-Deployment
[Kubernetes] Deployment Overview
Kubernetes 基礎教學(二)實作範例:Pod、Service、Deployment、Ingress
請問在創建Deployment的deployment.yaml檔案裏,沒有輸入replicaset,為什麼它會產生replicaset.apps/bar-deployment-75bcfbd655 和 replicaset.apps/foo-deployment-6bbf665b47,是否在deployment.yaml檔案裏輸入了spec.replicas: 1,它就會自動生成replicaset.apps/bar-deployment-75bcfbd655 和 replicaset.apps/foo-deployment-6bbf665b47?
沒錯 使用 deployment 代表 Kubernetes 已經幫你對 replicaset 做出抽象管理了,目前正常是不太需要設定到 replicaset 這個 workload 的